[New Model][Nvidia] Add SM12x support for DeepSeek V4 Flash with essential fixes#41834
[New Model][Nvidia] Add SM12x support for DeepSeek V4 Flash with essential fixes#41834jasl wants to merge 174 commits into
Conversation
|
@zyongye |
There was a problem hiding this comment.
Code Review
This pull request implements support for DeepSeek V4 on SM12x (Blackwell) architectures by providing Triton-based fallbacks for DeepGEMM-dependent operations. Key enhancements include the introduction of specialized Triton kernels for sparse MLA, FP8 einsum, and MQA logits, as well as memory optimizations in the sparse attention indexer to compute top-k indices without materializing full logits. Additionally, the PR updates the model loader to support weight name filtering for skipping MTP weights and handles Blackwell-specific FP8 quantization scales. I have no feedback to provide.
💡 Codex Reviewvllm/vllm/model_executor/layers/sparse_attn_indexer.py Lines 86 to 89 in 9596dbf This helper now disables the DeepGEMM requirement for every SM120 run, but the FP4 indexer cache path still depends on DeepGEMM kernels ( vllm/vllm/model_executor/model_loader/default_loader.py Lines 236 to 240 in 9596dbf The new pre-load ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
042e366 to
df2e6f8
Compare
…ore einsum alignment hint Two fixes for the DeepSeek-V4 sm12x Triton kernels, reported by GanyX19 (vllm-project#41834) from running this branch on GB10/sm_121 under load. 1) sm12x_mqa.py: the three MQA-logits kernels declared per-request-varying values (num_q/seq_len_kv, num_rows/logits_width, stride_lm) as tl.constexpr. Triton caches a distinct kernel per distinct value for the process lifetime; on GB10 the compile cache is unified memory, so it grows unbounded (~5-6 GB/h/ node) until the host hard-freezes. These are used only in boundary masks and address arithmetic (verified: never a tl.arange/range extent), so make them plain runtime args -> Triton specializes only on divisibility. stride_ln (=1) and all tile/shape constexprs stay. 2) fp8_einsum.py: this branch already keeps num_tokens/a_stride_group/ a_scale_stride_group runtime (to avoid the same leak) but without a tl.multiple_of hint, which drops them to divisibility=1 and costs ~24% single-stream decode. Both are provably div-by-16 (a_stride_group = T*hidden, hidden%128==0; a_scale_stride_group via align(T,4)*32), so hint them while keeping them runtime. Applied to both the BF16 and the fused-quant einsum kernels (same matmul, same strides). a_scale_stride_hidden / tf32 strides are NOT hinted (not div-by-16-provable). Co-authored-by: GanyX19
GanyX19 finding #3: _tf32_hc_prenorm_gemm_kernel has the same per-shape constexpr-recompile pattern (stride_outs/stride_sqs vary with M). Same constexpr->runtime treatment (address-arithmetic only, verified no arange/range extent); no multiple_of hint (store path, not div-by-16-provable). Minor contributor, material mainly at 1M context. Co-authored-by: GanyX19
|
@GanyX19 — both reproduced and verified on our GB10 (sm_121, 2-node TP=2), and all three are now landed on this branch ( #1 — MQA-logits A nice corroboration fell out of our A/B: in a controlled run the pre-fix (constexpr) build logged #3 — tf32 prenorm gemm. Took this too: same #2 — einsum alignment hint. Confirmed our branch already keeps Validation (GB10, DSv4-Flash MTP-2): correctness clean with the hints in — GSM8K-100 = 0.99, needle-recall 16/16 — so the Really appreciate the depth here — the mask/arithmetic-vs-extent distinction and the ÷16 safety analysis made these trivial to verify. Glad to have you running the branch on GB10. |
|
This pull request has merge conflicts that must be resolved before it can be |
|
Both fixes confirmed in production on our 4-node TP=4/EP=4 GB10 rig — thank you. And one remaining rough edge (long-context prefill latency) I'd love your read on. 9bccfd1 (persistent_topk) — wedge resolved. We've now run 11h+ at max_model_len=1048576 (full 1M) with zero wedges. The 1M-width cudagraph warmup — the exact spot that hard-failed on the oversubscribe 15434cb (MQA-logits constexpr leak) — the memory-creep headline win, confirmed. GanyX19's diagnosis was exactly our long-standing "unexplained ~2.4–3.5 MB/req creep to freeze." Post-fix, measured two ways: The rough edge — long-context prefill monopolizes the engine (~1–2 min). Now that 1M is enabled, a single large prefill stalls everything. A representative window (2 requests running, ~1M-scale context): Questions for you:
Happy to run a controlled prefill-latency-vs-context sweep on the 4-node rig if that'd help you characterize it — we're a real 1M/EP=4 production case. |
|
@wingcomm — this is fantastic to hear, thank you for the production confirmation. 11h+ at full 1M with zero wedges, the 1M cudagraph warmup capturing clean, and the ~5–6 GB/h → ~1.9 MB/h memory-creep cut (~3000×, "the 17h-restart treadmill is over") are exactly the outcomes we were hoping for — and the KV-pool bonus at 1M (4.63M vs 2.91M @512k from the C128A workspace self-sizing) is a nice surprise. Really appreciate you closing the loop with hard numbers on a real EP=4 rig. On the long-context prefill monopolization — good diagnosis, and your read is mostly right. Answers: 1. Is the streaming-radix cost inherent to 99 KB smem? — No, it's a conservative floor, and there's real headroom.
So: the re-stream is exact-and-safe, not minimal. I think it's optimizable and I'd like to prototype the finer-histogram/narrow variant — your sweep offer is exactly what would guide it (see below). 2. Scheduling lever — the reason chunked-prefill barely helped is structural, and it bounds the fix. 3. Is more of the prefill recoverable like the einsum? — No, different kind of floor. On the sweep — yes please. A controlled prefill-latency-vs-context sweep on your 4-node 1M/EP=4 rig would be genuinely useful: it'd confirm whether the top-k actually dominates the step (vs the sparse-MLA prefill / MoE) at 1M, and give me a target curve to validate a finer-histogram prototype against. If you can capture per-context-length step time (say 128K / 256K / 512K / 1M, single large prefill, idle otherwise) plus a py-spy/nsys sample of where the ~110s goes, that pins it. If the top-k is the bulk, I'll take a run at the finer-histogram variant and send you a patch to try. We don't have a 4-node/1M setup here (our GB10 validation is 2-node), so your rig is the real characterization platform. |
…e reads) The force_noncooperative long-context path (seq_len > 32768 on <128KB-smem parts like GB10) used histogram_streaming_topk: 4 full-row re-reads (one per MSD-radix round). Replace with a 2-pass variant of histogram_2048_topk (CacheBins=false): Phase 1 builds the 2048-bin histogram, Phase 2 re-reads from global (instead of the per-thread register cache that caps the 1-pass path at ~8K) and collects, Phase 3 exactly refines the buffered tie bin. 2 full-row reads vs 4. The finer 2048 bins keep the tie bin small (~seq/2048 << the 3708 buffer at 1M). Exact; falls back to the streaming radix if the fp16 tie bin overflows the buffer (never on GB10's ~1.9M max). Targets wingcomm's 1M-prefill top-k monopolization (PR#41834). Prototype — pending standalone bit-exact + perf validation.
|
@wingcomm — didn't wait for the sweep: took a run at the finer-histogram top-k and it's landed on this branch ( What it is. The 4-round streaming radix re-read the full row 4× (once per MSD-radix round). The new path ( Validated (GB10 2-node here):
The honest caveat, and where your sweep comes in. This is 2.4× on the top-k. Whether it moves your ~110 s prefill depends on how much of that step the top-k actually is — which is exactly what your prefill-latency-vs-context sweep + a py-spy/nsys sample would show. If the top-k dominates, you should see a real cut; if the sparse-MLA prefill / MoE dominate, less so (and those are the next targets). Would love the before/after on your 4-node/1M rig — pull |
forward_mqa passed seq_lens=None to the trtllm-gen sparse decode, so the kernel walked all topk_tokens block-table entries and masked the -1 padding that appears whenever a request's context < topk_tokens. Every sibling sparse-MLA backend (flashinfer_mla_sparse, flashattn_mla_sparse, flashmla_sparse) passes the per-token valid count instead, and upstream PR vllm-project#47527 proposes the same for this SM120 path. triton_convert_req_index_to_global_index already computes that valid count in the same kernel pass at no extra cost; request it (return_valid_counts=True) and forward it as seq_lens so the kernel reads only each request's valid prefix. Bit-identical to seq_lens=None on the long-context path (context >= topk_tokens => valid_count == topk_tokens); at short context it skips the padding tail instead of masking it. Primary value is convergence with the sibling backends and vllm-project#47527; perf-neutral on GB10 (interconnect-bound decode, long-context benchmarks carry no padding). Validated on GB10 2-node TP=2 MTP2 (mml 524288): arthur 434k conc1 2/2 (long, no padding), GSM8K 5-shot/200 = 0.96 (short, padding present -> new prefix-skip path), serve-side error scan clean.
|
Problem definition: long-context prefill is slow on GB10 (4-node TP=4/EP=4, 1M ctx) The root: a single large prefill is minutes long — no concurrency involvedOne request, ~536K prompt tokens, This is the problem. Everything else is a consequence of it. Where the ~9.5 min goespy-spy sampling the worker's MainThread across the prefill (8 samples):
So your Profile caveat: this is py-spy (samples where the Python thread sits — often parked in a Why it surfaces as a "stall with parallel requests"The several-minute single-prefill is the cost the submitter of a big request pays. It becomes a stall for everyone else under concurrency because So parallel-stall ≈ N × single-prefill-time. Chunked prefill doesn't hide it — the indexer top-k is over the accumulated context, so every late chunk still does ~full-context work. Fix space
|
|
This is excellent isolation work — thank you. It matches our read, and it correctly reframes where the top-k sits. Agreed on the shape. top-k ≈12%, and your ~7–15%-per-prefill math for the 2-pass histogram is right — it was always aimed at the top-k monopolization (the 4-read streaming blowup when a ~1M-context row overflows the register cache), never the prefill floor. MLA + MoE are the floor, and it's interconnect-bound — that lines up with our own 2-node decode teardown (MoE-GEMM + NCCL-AR + dense-GEMM as the top-3), just shifted toward attention on the prefill side. One caveat before we chase the 50/37 split — py-spy will over-attribute to whichever kernel the NCCL-wait parks in. On an interconnect-bound step the MainThread sits in One thing already pointing that way: your 4-node 573s is actually a hair slower than the 2-node 513s for the same ~535K — i.e. adding two nodes bought more collective latency than compute relief. For a single fat prefill that's a fingerprint of the comms-bound regime (the fabric, not FLOPs, is the near-term wall), and it says the node count that minimizes single-prefill TTFT may be the smallest one that still holds the KV — the extra nodes are there for 1M capacity, not speed. One structural note that the trace should confirm: the ~50% "sparse-MLA attention" in prefill is DSv4's MHA-style prefill path — a different kernel from the sparse-decode one the top-k / FI-SM120 work touches (the SM120 sparse backend is decode-only). So it's separately optimizable and, as you say, the higher-leverage target. On the MoE side (~37%), consumer-Blackwell defaults to Marlin W4A16; whether Marlin is the right kernel for these large prefill row-counts (vs a grouped-GEMM path) is an open question, and the natural large-batch MoE lever — DeepGEMM W4A8 — is currently blocked on SM120, so near-term it's Marlin or bust there. Since the step is comms-bound, the one no-rebuild knob worth a cheap A/B is NCCL tuning (algo/proto/channels for the fabric) — it won't touch kernel time but can shave the collective floor. On So: nsys next. A single-request 1M/536K prefill GPU timeline decides whether the effort goes to the prefill-MLA kernel, the MoE GEMM, or the comms floor — grab it whenever the maintenance window opens and we'll target from there. |
Flip VLLM_DEEPSEEK_V4_FLASHINFER_SM120_DECODE default False -> True. On SM12x with FlashInfer >= 0.6.14 present, the DSv4 attention selector now routes prefill + decode through the FlashInfer SM120 packed sparse-MLA kernel (trtllm_batch_decode_sparse_mla_dsv4) instead of our Triton/TileLang FlashMLA path. The selector still gates on SM12x + has_flashinfer_trtllm_sparse_mla_dsv4(), so non-SM12x and FI-missing boxes fall back to Triton unchanged; set the flag =0 to force the Triton FlashMLA path. A/B on FI 0.6.14 (GB10 2-node TP=2, build 616a572): - 8-32k: ~parity (no regression) - ctx_pp (prefill): +12.5% @128k, +18.1% @256k - pp2048 (prefill): +15% @128k, +16% @256k - tg128 (decode): +45% @128k, +81% @256k (Triton falls to 14 t/s, FI holds 25 t/s at 256k); both widen with context; 256k TTFT 248s -> 210s Correctness on the FI path: arthur 434k 2/2 + GSM8K 0.975 (== Triton). Triton FlashMLA stays the instant fallback.
@jasl Following up on the strict tool-calling grammar thread above — we hit what looks like a different xgrammar termination on a branch head that already includes Setup: 2× RTX PRO 6000 workstation (sm_120), TP=2, commit Symptom: in a 42-request structured-extraction batch (~8k-token prompts, concurrency 10), 7/42 returned Unlike the June report, the rejected ids vary per request (128825, 119061, 112434, 1, …) — it reads like draft tokens still being proposed after the matcher accepted the stop token, not a fixed stray tag. Triangulation (same workload): MTP off + thinking on → 42/42 OK. MTP on + thinking off ( Workaround we're shipping: thinking off whenever a function is forced via Disclosure: I'm not an expert in AI hardware or vLLM internals — this report was put together with extensive help from Claude Code. Numbers and logs are from real runs; the root-cause reading may be off, happy to be corrected. |
|
@jedelacarrera I will check tomorrow, thank you for the report! |
…rammar advance at the reasoning boundary (vllm-project#44297) Signed-off-by: Allen.Yu <yuyue0225sc@163.com> Signed-off-by: yue.yu <yuyue0225sc@163.com> Co-authored-by: Benjamin Chislett <chislett.ben@gmail.com> (cherry picked from commit e7c9df9)
|
@jedelacarrera Thank you — this was an excellent report, and your instinct was right on two counts: the workaround is sound, and the Reproduced on a 2-node GB10 (TP=2) with your configuration — MTP + Root cause (not the terminated matcher): every 500 is This is already fixed upstream by #44297 ("Constrain bitmask and trim grammar advance at the reasoning boundary"). I've backported it to this branch ( Validated on the same box: the identical 42-request workload → 42/42, 0×500, 0 terminated requests; and #44297's own test suite ( Please pull the latest branch head and retry. Your thinking-off-under-forced- One heads-up: after #44297 you may still see a few non-fatal |
Picks up vllm-project#47474 (dsv4 token_to_req_indices caching, 5-6x kernel) + 101 other upstream commits. 3 conflicts resolved keeping our validated DSv4 stack: - reasoning: keep our DeepSeekV4ThinkingReasoningParser (DSML implicit-</think> end-marker fix); do not adopt upstream's new vllm.parser.engine reasoning adapter for deepseek_v4 (separate validated follow-up if wanted). - dspark.py: keep our extended DeepSeekV4DSpark (markov draft + V1-optin); upstream's small _insert_context_kv/ForCausalLM refactor does not apply.
@jasl Confirmed on sm_120 (2× RTX PRO 6000, TP=2): cherry-picked fe486bf’s three files onto our 616a572 build — the previously-failing workload (thinking on, named tool_choice, MTP, ~8k prompts) now passes clean: [42/42, 0×500 / 10/10 smoke]. One note for others on this path: the other commit on the branch head (30283b9, FlashInfer SM120 default) requires a newer FlashInfer with flashinfer.mla._sparse_mla_sm120 — pulling the full head onto an older image crashes at model load; either update FlashInfer or take #44297’s files only. Thanks for the fast turnaround! |
nsys GPU-kernel trace — long-context prefill (the gating measurement)Build: 9f18be7 (PR #41834), 1M ctx, TP4/EP4, packed FlashInfer SM120 prefill+decode, GB10. HeadlineLong-context prefill is GPU-COMPUTE-bound (GPU ≈99.7% busy), NOT comms-bound. The dominant GPU-kernel time breakdown (rank0), two context sizes
Combined lightning-indexer (MQA-logits + top-k + compress): 57.5% → 65.0% as context grows. What this settles
Raw |
…-enable # Conflicts: # vllm/models/deepseek_v4/attention.py
…rnel) Upstream pins flashinfer-python/cubin==0.6.13, but PyPI 0.6.13 does not ship flashinfer.mla._sparse_mla_sm120 (the DeepSeek-V4 SM120 sparse-MLA decode kernel our fork's FlashInferMLASparseSM120 backend calls). Pin 0.6.14, which carries it; flashinfer-cubin 0.6.14 is GitHub-release-only (not on PyPI), so reference the release wheel directly. Prevents 'pip install -e .' from silently reverting a hand-installed 0.6.14 back to 0.6.13 on every rebuild.
|
@wingcomm This is a great trace — a per-kernel nsys split is exactly the measurement that settles the py-spy ambiguity, thank you. It lines up with what we've been seeing and sharpens it in one important way. On the attribution: our earlier py-spy had bucketed that ~52% as "MLA," but your nsys resolves it correctly — it's the lightning-indexer On your headline ask — a packed/fused prefill indexer, analogous to the SM120 decode indexer: we chased exactly this, and it looks like a hard floor recall-safely, so I'll save you the detour. The decode indexer packs because per step it scores 1×N; prefill is N×N. The sub-quadratic angles we tried all broke recall or were vacuous:
The kernel itself is already fp8 + tuned and compute-bound (not bandwidth) — fusing the top-k into it is only ~4%. So the realistic prefill wins there are constant-factor, not complexity; the O(N²) is inherent to top-k-selecting over the full context. On NCCL: agreed, low prefill upside — comms at 5–7% and falling is consistent with what we see (prefill is compute-bound, the fabric isn't the wall). Decode is the other story: that is the interconnect-latency-bound regime for us on 2-node, so I'd be very interested in how your multi-QP ( Would love the |
VLLM_DEEPSEEK_V4_FLASHINFER_SM120_DECODE has defaulted True since the flip (envs.py:198); the two docstrings still said 'Default off'. Doc-only, no runtime change. (The >=0.6.13 RuntimeError message is correct — that's the trtllm_batch_decode_sparse_mla_dsv4 / PR3395 minimum — left as-is.)
|
Thanks — and thank you for the indexer floor analysis; that saves us a real detour. Agreed On the multi-QP decode A/B you asked for — it's neutral. No measurable movement. Controlled decode benchmark (short prompt, 128 output tokens, temp 0.7, median of 3 trials/conc)
If anything OFF is a hair faster at conc-1, but it's inside trial noise — call it a wash. The The decode numbers for reference: ~50 tok/s/req at conc-1 (ITL ~44 ms, MTP giving ~2 tok/step), Attaching the full per-kernel GPU-time tables ( `=== multi-QP ON (NCCL_IB_QPS_PER_CONNECTION=4) — decode benchmark, 3 trials/conc === === multi-QP OFF (base recipe, no QP line) — decode benchmark, 3 trials/conc === === VERDICT: multi-QP (NCCL_IB_QPS_PER_CONNECTION=4) is NEUTRAL on decode === dsv4-9f18be76-prefill-200k-kernels.csv |
|
This pull request has merge conflicts that must be resolved before it can be |
Summary
This PR enables DeepSeek V4 Flash on SM120/SM121 Blackwell client hardware by carrying the SM12x fallback and tuning stack needed for the current vLLM V1 path. It is intended for RTX PRO 6000 Blackwell Workstation Edition, RTX 5090-class SM120, and GB10 / DGX Spark SM121 users who cannot use SM100-only TMEM /
tcgen05kernels.This branch is reconciled on top of the merged #43477 and provides the stock-deps path: DeepSeek V4 on SM120/121 that builds and serves on released FlashInfer / DeepGEMM wheels, complementing #43477's route that needs the unreleased FlashInfer #3395 + DeepGEMM #324 dependency branches. It is kept synced onto current
upstream/mainand now also carries DSpark spec-decode support (self-drafting block-5) alongside the default MTP2 path. Latest validated head is tagsm120-pr-41834-stable-preview-20260705(9f18be7630). See Update 2026-07-05 (a 2-pass finer-histogram top-k that ~halves the reads on the GB10 long-context indexer path), Update 2026-07-04 (a GB10 unified-memory Triton recompile leak → hard-freeze fix + an fp8-einsum alignment recovery, reported by @GanyX19) and Update 2026-07-03 (DSpark, the V2 padded-Q OOM fix #26, thepersistent_topkGB10 long-context fix, llama-benchy baselines) below, and the earlier dated updates for history.Change footprint — model kernels vs. core-vLLM touch points
The branch splits cleanly into model/kernel code and a small set of core-vLLM integration points (116 files, +15.5k/−0.4k, of which ~+3.5k is tests):
vllm/models/deepseek_v4/**plus the SM12x sparse-MLA decode / indexer / DeepGEMM kernels that live in shared dirs (v1/attention/backends/mla/sparse_mla_kernels.py,model_executor/layers/sparse_attn_indexer.py,v1/attention/backends/mla/{indexer,sparse_swa}.py,utils/deep_gemm.py,kernels/mhc/tilelang.py), the new DSv4 reasoning parser / tokenizer, and device tuning JSONs.models/deepseek_v4/sparse_mla.py, perf, 2026-06-26) —_c128a_effective_topk_widthnow takes the max position from the CPU-sideCommonAttentionMetadata.max_seq_leninstead of a per-stepint(positions.max().item())device sync, dropping a launch-stream stall on every C128A metadata step (reported via gdb native stacks by a GB10/TP4 user). Decode is identical (max_seq_len-1 == positions.max()); only chunked prefill sees a safe, slightly-wider 128-aligned top-k.single_type_kv_cache_manager.py(+243),kv_cache_coordinator.py(+67),kv_cache_manager.py(+60),sched/scheduler.py(+1)cache_blockstail-block-reuse rewrite. (Our earlierblock_poolstale-hash reset is dropped in the reconcile — subsumed by upstream's own unconditional reset, which arrived via theupstream/mainmerge.)v1/spec_decode/llm_base_proposer.py(+173)fused_moe.py(+65),oracle/mxfp4.py(+43),routed_experts.py(+33),experts/flashinfer_cutlass_moe.py(+27),quantization/mxfp4.py(+12),oracle/nvfp4.py(+1)quantization/utils/fp8_utils.py(+99),linear/scaled_mm/{cutlass,marlin}.py(+45/+16),csrc/.../marlin_moe_wna16/ops.cu(+10, the only C++)config/vllm.py(+44),compilation/breakable_cudagraph.py(+22),passes/utility/fix_functionalization.py(+12),config/compilation.py(+11)chat_completion/protocol.py(+101),serve/render/serving.py(+28),tool_parsers/structural_tag_registry.py(+16),chat_utils.py(+11),engine/protocol.py(+9),chat_completion/{serving,batch_serving}.py(+8/+6),reasoning/__init__.py(+4)reasoning_content/thinkingparam / tool-call streaming (jasl#19 instruction-following)model_executor/warmup/kernel_warmup.py(+617)weight_utils.py(+43),default_loader.py(+16)envs.py(+63),utils/flashinfer.py(+16),utils/import_utils.py(+9),v1/worker/{gpu_model_runner,ubatch_utils}.py(+12/+12)VLLM_DEEPSEEK_V4_*flags +has_cutedsl/has_flashinfer_trtllm_sparse_mlaprobesTwo notes for review:
kv_cache_coordinatorcache_blocksrewrite (affects hybrid-KV models; validated ≥ prior behavior), the MTP proposer base-class change, and the OpenAI-entrypoint plumbing. Everything else (MoE oracle, fp8_utils, cudagraph gate, warmup, envs) is arch / quant / env-gated and inert for other models.Duplicate-work check
Open PR search was refreshed on 2026-06-12 for SM120 / SM12x / DeepSeek V4 / GB10 terms. The nearest open PRs are related but not duplicates:
42657aca65) and carries the stock-deps DSv4 SM120/121 path that runs on released wheels, complementing #43477's fork-deps route. See Update 2026-06-23 — #43477 reconciliation below.Fixed preview tags
These tags are in
jasl/vllmand give users stable pins while the PR is still moving:sm120-pr-41834-stable-preview-202607059f18be763020260704head + a 2-pass finer-histogram top-k for the GB10 long-context indexer path (see Update 2026-07-05): 2 full-row reads instead of the streaming radix's 4, exact (falls back to streaming if the pivot bin overflows). Validated GB10: bit-exact vstorch.topk(64K–1M × spread/dense/ties), 2.43× single-row kernel at 1M, arthur 434K recall 2/2.sm120-pr-41834-stable-preview-20260704b43470e87120260703head + @GanyX19's two GB10 kernel fixes (see Update 2026-07-04): MQA-logits + tf32-prenorm per-shapeconstexpr→runtime (stops the unbounded Triton recompile that leaks unified memory ~5–6 GB/h/node and hard-freezes GB10 under load), and the fp8-einsumtl.multiple_of(16)alignment recovery (~24% single-stream decode at long context). GB10 validated: GSM8K-100 0.99, arthur 16/16, decode-neutral at ≤32K; DSv4-Flash MTP2 + DSpark llama-benchy re-run normal.sm120-pr-41834-stable-preview-20260703444fe3ac8bupstream/mainsyncs since 06-26 (307 commits, incl. MRV2 scheduler #46974 + spec-decode int64 #47383), the V2 padded-Q OOM fix (#26) (which also reopened V2 long-context recall), and an exact non-cooperativepersistent_topkfor <128 KB-smem parts (fixes GB10 ≥400k long-context hard-fail). GB10 SM121 validated: GSM8K-200 0.965, arthur 434k 2/2 + conc12 24/24; DSv4-Flash MTP2 and DSpark both serve. llama-benchy446dd42fbaselines in Update 2026-07-03 below.sm120-pr-41834-stable-preview-20260626c766cbc6ffupstream/main(198 commits since the #43477 merge; our NVFP4FLASHINFER_CUTLASS-clamp fix landed upstream as #46492 → fork patch dropped) + the C128A metadata device-sync removal. 6 conflicts resolved; upstream's newcooperative_topk(#43008) gated off SM12x (capability family 120) to keep the validated decode path byte-identical. Validated dual-arch — RTX SM120 GSM8K-200 0.97 + #19 PASS; GB10 SM121 GSM8K-200 0.945 + arthur 64/64 + Forum53 PASS + llama-benchy prefill +80% / decode flat. See Update 2026-06-26 below.sm120-pr-41834-stable-preview-20260623f7b4b425b042657aca65ofupstream/main), keeping the stock-deps DSv4 SM120/121 path that runs on released wheels. Two reconcile regressions fixed — DeepGEMM no longer auto-enabled on SM120 (a94657e601, the pinned ref asserts), and #43477's prefill-SWA launch is gated + the kernel OOB clamped (f7b4b425b0). Validated dual-arch (see Update 2026-06-23 — #43477 reconciliation below).sm120-pr-41834-stable-preview-20260622b5ba0f19f02output.size(0)==num_tokens (84 vs 83)whenVLLM_DEEPSEEK_V4_FLASHINFER_SM120_PREFILL=1), (2) MTP draft logits cast to float32 before top-k/top-p sampling (fixes an engine-killing assertion on any MTP + non-greedy sampling, gate-independent). See Update 2026-06-22 below.sm120-pr-41834-stable-preview-2026062172261a7af149fa5d3fe2ed2b9956e92590731012sm120-pr-41834-stable-preview-20260620a743ef5dfbd16cad0b9a628773c0c1d1841f1790sm120-pr-41834-stable-preview-20260612075245f32247a5a695fa8979d61837bf6b87da897dcb7dsm120-pr-41834-fallback-before-replacement-202606120537205d1584e2de2b3c64540e70dfc370b0211eb6b2fcUpdate 2026-07-05 — 2-pass finer-histogram top-k for the GB10 long-context indexer path
Head
9f18be7630(tagsm120-pr-41834-stable-preview-20260705). Theforce_noncooperativelong-context path (seq_len > 32768 on <128 KB-smem parts like GB10) used the exact single-CTA streaming radix, which re-reads the full row 4 times (once per MSD-radix round) — measurably the dominant cost of the sparse-indexer top-k at very long context (a GB10/1M user reported a single ~1M prefill monopolizing the engine ~110 s).Replaced it with a 2-pass variant of the existing
histogram_2048_topk(persistent_topk.cuh), via a newCacheBinstemplate flag: Phase 1 builds the 2048-bin histogram, Phase 2 re-reads from global (instead of the per-thread register cache that caps the 1-pass path at ~8 K) and collects, Phase 3 exactly refines the buffered pivot bin. 2 full-row reads instead of 4. The finer 2048 bins keep the pivot bin small (~seq/2048 ≈ 488 at 1M, well under the 3708-entry tie buffer), so it stays single-CTA and 99 KB-smem-safe. If the pivot bin overflows the buffer (fp16 bins clustering — e.g. thousands of exactly-equal logits), it falls back to the streaming radix, so it is provably exact for any distribution / seq_len. ≥128 KB devices are unaffected (they take the cooperative radix path).Validated (GB10 SM121):
torch.topkacross 64K–1M × {randn-spread, clustered dense-pivot, >4096 exact-ties} — value multiset identical, no dups, fallback exercised and exact.max_model_len=524288, arthur needle-recall at ~434K = 2/2, no oversubscribe/errors — correct in the real indexer.This is a ~2.4× speedup on the top-k; the end-to-end long-context prefill impact depends on how much of the prefill step the top-k dominates (workload-dependent, characterization in progress with the reporting user).
Update 2026-07-04 — GB10 Triton recompile-leak / hard-freeze fix + fp8-einsum alignment recovery (thanks @GanyX19)
Head
b43470e871(tagsm120-pr-41834-stable-preview-20260704) = the20260703head + two kernel fixes reported by @GanyX19 from running this branch under sustained load on GB10 / DGX Spark (sm_121), both invllm/models/deepseek_v4/nvidia/ops/:constexprTriton recompilation → host-memory leak → hard-freeze. The three MQA-logits kernels insm12x_mqa.py(and the_tf32_hc_prenorm_gemm_kernel) declared per-request-varying values (num_q/seq_len_kv,num_rows/logits_width,stride_lm;stride_outs/stride_sqs) astl.constexpr. Triton caches a distinct compiled kernel per distinct value for the process lifetime; on GB10 the compile cache lives in the same unified memory as the model, so it grows unbounded (~5–6 GB/h/node under production-like load) until the host hard-freezes. These values are used only in boundary masks and address arithmetic — never as atl.arange/rangeextent (verified) — so they're now plain runtime args (Triton specializes only on divisibility).stride_ln(=1) and all tile/shape constexprs stay. In a controlled A/B the pre-fix build even logged_tf32_hc_prenorm_gemm_kernelJIT-compiling during inference and then died withEngineDeadErrorunder the benchmark — the recompile pathology biting live; the fixed build ran the same load clean.fp8_einsum.pyalready keeps the group strides runtime (to avoid the same leak) but without atl.multiple_ofhint, which drops them to divisibility=1 and narrows the load — ~24% single-stream decode at 256K per @GanyX19's bisection. Restoredtl.multiple_of(_, 16)ona_stride_group/a_scale_stride_group(both provably ÷16:a_stride_group = T·hidden,hidden % 128 == 0;a_scale_stride_groupviaalign(T,4)·32) in both the BF16 and fused-quant einsum kernels.a_scale_stride_hidden/ tf32 store strides are not hinted (not ÷16-provable — a falsemultiple_ofcorrupts memory).Validated (GB10 SM121, 2-node TP=2): GSM8K-100 0.99, arthur needle-recall 16/16 — the alignment hints don't corrupt. Decode is throughput-neutral at the ≤32K benchmark sizes (the −24%/recovery is a 256K single-stream effect not exercised by the standard bench); the host-memory-leak → freeze fix is the headline win, and it's decode-neutral, so it's a clean take. The kernels are Triton (
@triton.jit, pure Python) — no C++ rebuild.llama-benchy re-run on
b43470e871(same pinned standard as below,446dd42f):Consistent with the
20260703numbers below (within GB10 run-to-run variance) — the kernel fixes are decode-neutral.Update 2026-07-03 — DSpark spec-decode, upstream syncs, V2 padded-Q + GB10 long-context fixes, fresh llama-benchy baselines
Latest validated head
444fe3ac8b(tagsm120-pr-41834-stable-preview-20260703), 307 commits past the 06-26 tag. Highlights:upstream/mainacross several merges (latest picks up the Model-Runner-V2 scheduler req-slot fix [BugFix][MRV2] Ensure all req slots are accounted for when scheduling #46974 and the MRV2 spec-decode int32→int64 block-verify overflow fix [Bugfix][Model Runner V2][Spec Decode] Fix int32 offset overflow in block verification kernels #47383). DSv4 is MoE so it stays on the V1 runner by default (upstream [ModelRunner V2] Enable by default for all dense models #44443 made V2 the default only for dense models); V2 is validated and reachable viaVLLM_USE_V2_MODEL_RUNNER=1but not the default.persistent_topkfor <128 KB-smem parts (GB10): the sparse-indexer top-k's cooperative-radix "large" path (seq_len > 32768) needs all CTAs co-resident and falls back to a path that asserts ≥128 KB smem/block — which GB10 (SM121, ~99 KB smem) violates, hard-failing long-context serving ≳400k. Fixed by gating on smem capacity (not capability family — RTX 50-series is also family-120 with ~100 KB) and routing those parts to an exact single-CTA streaming radix (no co-residency, no cooperative barrier). ≥128 KB devices (Hopper, datacenter Blackwell) are byte-identical. This also addresses the intermittent long-context wedge reported on 4-node GB10 in the comments.Fresh llama-benchy baselines (GB10 SM121, 2-node TP=2)
Pinned standard: fp8 KV, prefix-cache ON,
FULL_AND_PIECEWISE,max-model-len 49152, util 0.85,max-num-seqs 64,max-num-batched-tokens 8192; llama-benchy446dd42f(--pp 2048 --tg 128 --depth 8192 16384 32768 --concurrency 1 --runs 3 --enable-prefix-caching). Build444fe3ac8b.DeepSeek-V4-Flash + MTP2 (production config):
DeepSeek-V4-Flash-DSpark (self-drafting block-5, V1 runner, same knobs):
DSpark serves cleanly on GB10 2-node. On this Gutenberg book-continuation workload its draft acceptance is content-limited (~1.8–3.0 mean accept length over its 5 draft positions), so its decode is comparable to MTP2 here; DSpark's higher-acceptance advantage shows on more predictable / reasoning-style content (and the V2 speculator path accepts more still). Decode on multi-node GB10 is interconnect-latency-bound (GPUDirect RDMA is unavailable on GB10's unified memory → NCCL-over-TCP), which caps single-stream decode regardless of runner.
Running DSpark
DSpark is DeepSeek's self-drafting speculative-decode variant (draft weights carried in the target checkpoint, block size 5). Serve the DSpark checkpoint with the
dsparkspeculative method:vllm serve deepseek-ai/DeepSeek-V4-Flash-DSpark \ --tokenizer-mode deepseek_v4 --trust-remote-code \ --tensor-parallel-size 2 --kv-cache-dtype fp8 \ --speculative_config '{"method":"dspark","num_speculative_tokens":5}'num_speculative_tokensmust be 5 (the checkpoint'sdspark_block_size); the draft is self-hosted, so no separate--speculative-modelis needed.VLLM_USE_V2_MODEL_RUNNER=1; V2's long-context recall is now correct after the Add CUDA graph-based all reduce launcher #26 padded-Q fix.--gpu-memory-utilization/--max-model-lenaccordingly.Update 2026-06-26 — synced onto upstream/main + dual-arch revalidation
Re-synced the branch onto current
upstream/main(mergec7a4386a45, then the C128A device-sync hoist →c766cbc6ff; 198 upstream commits since the #43477 merge). 6 conflicts resolved:oracle/nvfp4.py— union the SwiGLU-clamp backend set to{TRTLLM, CUTLASS, MARLIN}. OurFLASHINFER_CUTLASSclamp fix landed upstream as [Bugfix] Allow flashinfer_cutlass as a clamped NVFP4 MoE backend #46492, so the fork patch is now redundant.routed_experts.py— combine the two per-tensor-scale loaders into one helper (our e8m0 bitwise view and upstream's 0-D/shape-(1,)_to_scalarnormalization).serve/render/serving.py+renderers/online_renderer.py— upstream's [Frontend] Split ServingRender into renderer and entrypoint. #44285 splitServingRenderinto renderer + entrypoint; our DSv4thinking→template-kwargs threading is re-homed onto the new structure (sampling-params site inServingRender.render_chat_request, prompt-render site inOnlineRenderer.render_chat).sparse_attn_indexer.py— preserve our SM120 short-row / persistent top-k path, and add upstream's newcooperative_topk([Perf][DSv4/DSv3.2] Add cluster-cooperative topK kernel for low-latency scenarios #43008) gated to exclude capability family 120, so SM12x decode is byte-identical to the validated path (enablingcooperative_topkon SM12x is a separate, to-be-validated perf experiment).engine/protocol.py(keep bothDeltaMessagehooks) andtests/models/test_deepseek_v4_mega_moe.py(keepCompilationConfig()fixture).Inherited for free from the sync: deepseek_v2 redundant-clone removal (#46651), sampler int32-overflow fix (#46560), spec-decode correctness (#45956 / #46533).
Validation — full matrix, both arches, on
c766cbc6ff:The MoE backend on both arches is Marlin (W4A16) —
is_deep_gemm_supported()is False on stock SM12x wheels, so the DeepGEMM/W4A8 path isn't selected and Marlin is the default; it is GSM8K-correct on both SM120 and SM121. The prefill +80% is inherited from upstream's prefill / scheduler / block-pool work (not an SM12x change of ours); decode is flat because our SM12x decode path is preserved unchanged.Update 2026-06-23 — #43477 reconciliation (stock-deps path) + dual-arch revalidation
Upstream merged #43477 (DeepSeek V4 + GLM-5.1 on SM120 via the FlashInfer-SM120 sparse-MLA route) on 2026-06-22. As merged it does not run on released wheels: its SM12x attention class raises at model construction unless the unreleased FlashInfer #3395 fork symbols are present, and it auto-enables a DeepGEMM MXFP4 path whose pinned ref (
#324) asserts on SM120. This PR is now reconciled on top of it so the two coexist: #43477 is the fork-deps route; this PR is the stock-deps route that runs on released FlashInfer / DeepGEMM wheels.Reconciliation is a merge of
upstream/maininto the PR branch (42657aca65, 6 conflicts resolved — kept our env/availability-gated SM120 decode route + both FlashInfer probes + #43477's gated prefill-SWA mechanism; dropped our now-redundantblock_poolstale-hash reset in favour of upstream's), plus two fixes for regressions the merge introduced:a94657e601). Enable DeepSeek V4 and GLM-5.1 on SM120 #43477 added SM120 tosupport_deep_gemm, so the engine selected a DeepGEMM MXFP4 kernel whose pinned/released ref aborts at init (Assertion sf.size(-2)==ceil_div(mn,gran_mn)). SM120 now falls back to Marlin/cutlass as before (needs the unmerged DeepGEMM GPTBigCodeForCasualLM support doesn't work #324 to enable).f7b4b425b0). The merged paged prefill-SWA index kernel launched unconditionally and computedblock_tableaddresses for masked-off tail lanes of deep (32k) prefill rows, which SM12x + Triton 3.6 faults as an illegal address even though the load is masked →cudaErrorLaunchFailureunder concurrent load. The launch is now gated behindVLLM_DEEPSEEK_V4_FLASHINFER_SM120_PREFILL(default off → the stock decode-only path never launches it) and the kernel's masked lanes are clamped.Validation — metrics flat vs the pre-reconcile head (
5ba0f19f02), both arches, DeepSeek-V4-Flash, fp8 KV, MTP=2:2.30.7)--async-schedulingA/B (off vs on), incl. 64k concurrency + sampling stressThe earlier "
--async-schedulingregression" note is withdrawn — that crash was this same prefill-SWA OOB, fixed above; with the fix,--async-schedulingon/off are equivalent and crash-free through a 64k concurrency-8 sampling soak.Update 2026-06-23 — GB10 / DGX Spark (SM121) long-context frontier, 256k–1M
Cold-prefill capability sweep on 2× GB10 / DGX Spark (SM121), TP=2 over RoCE,
max-model-len 1048576,gpu-memory-utilization 0.75, MTP=2, fp8 KV, EP-off, prefix-cache disabled,FULL_AND_PIECEWISE, greedy, C=1 (post-audit head). All five points complete cleanly (0 failures, no OOM / crash). KV cache = 1,868,754 tokens (5,964 bytes/token); a 1M-token request admits at 1.78× concurrency at this utilization.The multi-minute cost is the cold prefill (TTFT), which is GPU-bound (GPU
96% throughout each TTFT window) and scales super-linearly (O(N^1.4): 256k→512k = 2.45×, 512k→1M = 2.89×).A dedicated decode-vs-context sweep (256-token generation at 16k / 64k / 256k / 512k) shows the opposite for generation: steady-state decode is essentially flat with depth — median inter-step latency ~61–69 ms across 16k→512k (≈30–45 tok/s effective with MTP), i.e. throughput does not meaningfully degrade as context grows. That matches the per-step cost being dominated by fixed MoE GEMM + the 2-node RoCE all-reduce rather than the depth-dependent (
O(N)) indexer. (Decode rates from a 16-token TTFT-only run are not reliable — too short, plus MTP bundling — so they are not used here.)So the long-context penalty is concentrated entirely in the one-time cold prefill (TTFT above), not in generation: prefill is GPU-bound compute/bandwidth (LPDDR5X ~273 GB/s) plus the per-chunk 2-node RoCE all-reduce (no inter-node NVLink), while decode stays ~constant per token. MTP keeps ~2.0 acceptance at depth. Practically: GB10 suits large-context-in → generation-out when the one-time cold first token (minutes at 384k+) is acceptable or amortized by prefix caching; once generating, throughput is depth-independent. The 1M cold TTFT is ~20% faster than the 2026-06-06 baseline (2789 s vs 3504 s).
Update 2026-06-22 — long-context (256k+) crash fixes (latest validated head)
Two distinct crashes were reported at long context (≥256k, MTP=2). Both are fixed on
sm120-pr-41834-stable-preview-20260622b(5ba0f19f02); the rest of the branch is unchanged from the 2026-06-21 audit head:Packed-prefill output slice (
output.size(0)==num_tokens,84 vs 83). WithVLLM_DEEPSEEK_V4_FLASHINFER_SM120_PREFILL=1, the packed_forward_prefillpath sliced the query tonum_prefill_tokensunder MTP/cudagraph padding but passed the unsliced padded output to the kernel, which derivesnum_tokensfromq.shapeand assertsoutput.size(0)==num_tokens→ crash, cascading to an illegal-memory-access. The output is now sliced symmetrically. This path is gated (default FlashMLA prefill loops overq.shapeand has no such assert → was never affected). Interim workaround for older builds: setVLLM_DEEPSEEK_V4_FLASHINFER_SM120_PREFILL=0.MTP draft-sampler float32 cast (engine-killing assertion on non-greedy sampling). The MTP probabilistic draft sampler fed bf16 draft-head logits into the Triton top-k/top-p kernel, which asserts
logits.dtype==torch.float32→AssertionErrorkills the worker (and cascades to CUDA errors on TP peers). This fires on any MTP + non-greedy (top-k/top-p/temperature) request and is independent of the FlashInfer gates — so it reproduces "even without FlashInfer". Greedy decoding returns before the sampler, which is why greedy GSM8K validation never surfaced it. The draft logits are now cast to float32 (matching the main sampler). Validated on 2x RTX PRO 6000 (SM120): a 256k sustained sampling soak (temperature 0.7 / top_p 0.9, 9 concurrent workers, MTP=2, EP) that crashed on the first sampled request now runs clean (200+ requests).Note for very long contexts (e.g.
--max-model-len 500000): the sparse-MLA / indexer workspaces are sized bymax_model_lenand are not yet SM12x-arch-gated, so they consume a large fixed share of VRAM and leave a thin KV budget — see #42856 for the workspace-shrink fix. This is a memory-headroom concern, separate from the two crashes above.Update 2026-06-21 — post-audit cleanup
This supersedes the 2026-06-20 and 2026-06-18 heads and the earlier validation data below. An audit of the SM12x branch against current upstream removed redundant, disproven, and experimental deltas; the cleaned head is validated metrics-flat (marginally better) on 2x RTX PRO 6000 Blackwell (SM120) and 2-node GB10 / DGX Spark (SM121), DeepSeek-V4-Flash, fp8 KV, MTP=2. The jasl#19 (instruction-following) and #45309 breakable-cudagraph-garbage revert (#45972) correctness fixes are retained. Five changes:
Breakable-cudagraph stays default OFF (
FULL_AND_PIECEWISE). DeepSeek-V4 is deliberately excluded from breakable-cudagraph auto-enable — on real 2x GB10 MTP decode breakable regressed throughput and degraded as output length grew (≈31→19 tok/s at 400→800 max-tokens vs a flat ≈40). The gate is now a single MiniMax-only helper instead of a dead always-False stub; behavior is unchanged. (VLLM_USE_BREAKABLE_CUDAGRAPH=1still opts in.)The long-context recall fix is the int64 block-offset cast, not a cache fence. The 2026-06-20 head attributed the MTP high-concurrency recall/garble bug to a missing copy-on-write on writable caches and added a prefix-cache write-completion fence. Further investigation showed that hypothesis was wrong: the actual cause is an int32 overflow of the packed-KV block offset in the SM12x paged-MQA-logits indexer kernels, fixed by an int64 cast (retained). With the int64 fix in place the write fence is redundant — a fence-OFF recall gate holds 8/8 @ conc=8 and 16/16 @ conc=16 (0 miss) on RTX, and 8/8 on GB10. The write-completion fence and the COW broadening paired with it were therefore removed.
Removed the scheduler prefill-fairness heuristics (ungated, generic very-long-prefill / mixed-decode chunk-limiting). They targeted a decode cliff later re-diagnosed as MoE-GEMM + NCCL-all-reduce bound (not schedulable) and were not load-bearing: a cleanup-vs-prior A/B shows an identical mixed prefill/decode fairness ratio (0.716 vs 0.714) and equal inter-chunk latency.
Moved the experimental
VLLM_NVFP4_GEMM_BACKENDb12x research lever out of the PR (off-by-default, unused on the shipped path) and dropped a tool-calling-env diff-reflow churn.Net vs the 2026-06-20 head: 6 files, −833 lines (the removed fence + scheduler heuristics + their tests). The decode/prefill CUDA kernels are byte-identical across the cleanup, so the gated-decode-optimization profile and the 2026-06-12 throughput baselines below are unchanged.
Validation, 2026-06-21
Trivial-prompt generation (cudagraph sanity), both platforms:
2+2 → 4,7*8 → 56,capital of France → Paris— no garbage.Default decode path, MTP=2:
The GB10 SM121 run is a from-scratch 2-node rebuild of the cleaned head (NCCL
2.30.7re-pinned per node); arithmetic, GSM8K, and the long-context recall gate all pass, confirming the fence removal holds recall on SM121 as well. The recall fix is the int64 cast in the SM12x indexer kernel, so the 2026-06-12 throughput baselines below are unchanged.llama-benchy (eugr format), GB10 2-node / SM121, MTP=2, prefix-cache on (GB10 MTP decode/prefill profile — unchanged across the 2026-06-21 cleanup, decode kernel byte-identical):
Prefill 1595–1722 tok/s at depth; decode 40 tok/s @ C=1 holding 38–42 out to 32K context (no decode cliff); prefix-cache hit 42–46% under MTP.
Gated SM120 decode optimization (
VLLM_DEEPSEEK_V4_FLASHINFER_SM120_DECODE=1)The decode gate uses
flashinfer.mla._sparse_mla_sm120(in FlashInfer main / 0.6.13; absent from the 0.6.12 release). Installing it correctly matters: a barepip install --upgrade flashinfer-python @ git+mainbumpsflashinfer-pythonbut leaves a staleflashinfer-cubin/flashinfer-jit-cache, and FlashInfer then raises a version-mismatch error at startup (and re-JITs kernels). Uninstall the precompiled packages first, then upgrade:pip uninstall -y flashinfer-jit-cache flashinfer-cubin pip install --upgrade "flashinfer-python @ git+https://github.com/flashinfer-ai/flashinfer.git"For a reproducible pin instead of tracking moving
main, install matchingflashinfer-python+flashinfer-cubinnightlies (e.g.0.6.13.dev20260619, a bit-identical decode kernel to the validated build) — again uninstallingflashinfer-jit-cachefirst.RTX SM120, decode gate ON vs OFF, ctx0 decode (aggregate tok/s, 0 errors all rows):
gate-ON @C64 = 2814.8 tok/s matches the community target (~2815). The decode CUDA kernel is byte-identical across the rebase, so this profile is unchanged.
The default path needs no FlashInfer update. With the gate off (default), the import is lazy/gated, so FlashInfer 0.6.12 (official) works unchanged. On GB10 / 2-node, also pin
nvidia-nccl-cu13==2.30.7(a rebuild reverts it; a per-node mismatch hangs the NCCL handshake).Gated SM120 prefill optimization (
VLLM_DEEPSEEK_V4_FLASHINFER_SM120_PREFILL=1)Symmetric to the decode gate, prefill has an opt-in packed FlashInfer sparse-MLA path:
VLLM_DEEPSEEK_V4_FLASHINFER_SM120_PREFILL=1(default off; ~+5–6% single-stream prefill). With it off, prefill defers to the default FlashMLA indexed-D512 path. It routes through the sameflashinfer.mla._sparse_mla_sm120kernels as the decode gate, so it carries the identical FlashInfer version requirement — the install/pin steps above apply unchanged (FlashInfer main / 0.6.13; the default-off path needs no FlashInfer update and runs on 0.6.12). Decode and prefill share one FlashInfer build; there is no separate version to track for prefill.Branch validation, 2026-06-12
Base and head:
8a91228dbe363d1d113deb2a82e289429130dd01f32247a5a695fa8979d61837bf6b87da897dcb7dCommands run on the final head:
git diff --check upstream/main...HEADupstream/main..HEADSigned-off-byVLLM_TARGET_DEVICE=empty .venv/bin/python -m compileall -q vllm/envs.py vllm/model_executor/warmup/kernel_warmup.py vllm/models/deepseek_v4 vllm/v1/core vllm/v1/attention/backends/mla vllm/reasoning/deepseek_v4_reasoning_parser.py tests/test_envs.py tests/v1/core/test_prefix_caching.py tests/v1/core/test_scheduler.py tests/reasoning/test_deepseekv4_reasoning_parser.py tests/quantization/test_sm12x_tuned_config_lookup.py.venv/bin/python -m pytest tests/test_envs.py::test_deepseek_v4_sparse_mla_stats_path_env -qon the remote vLLM environment1 passed, 16 warningspython3 -m pytest tests/test_scripts.py -qin the public harness128 passed in 14.41sLocal vLLM pytest/ruff were not run on the Mac checkout because its
.venvdoes not currently includetorchorruff. GPU-path validation remains remote SM120/SM121-only.Latest clean SM120 RTX PRO 6000 x2 data, 2026-06-12
Artifact roots:
artifacts/codex_pr_stable_preview_f32247a/2x_rtx_pro_6000_sm120/rtx_current_pr_short_throughput_mtp_noep_20260612084721artifacts/codex_pr_stable_preview_f32247a/2x_rtx_pro_6000_sm120/rtx_current_pr_clean_mtp_noep_20260612080629Short-throughput profile:
max_model_len=131072,gpu_memory_utilization=0.975,max_num_batched_tokens=4096,max_num_seqs=24.FULL_AND_PIECEWISE, 80 prompts per concurrency.server_startup=0,bench_hf_mt_bench=0,bench_random_prefill_sweep=0.HF MT-bench, 80 prompts:
Random prefill sweep, C=1, output length 128, 8 requests per case:
Correctness and reliability profile:
max_model_len=131072,max_num_seqs=4,max_num_batched_tokens=4096.server_startup=0,bench_hf_mt_bench=0,eval_gsm8k=0,bench_random_prefill_sweep=0,bench_random_8000x1000=0,bench_random_256x256=0.GSM8K 5-shot, limit-200,
/v1/completions, MTP=2, concurrency 4:Additional 128K-profile random checks:
Latest clean GB10 / SM121 data, 2026-06-12
Artifact root:
artifacts/codex_pr_stable_preview_f32247a/2x_gb10_sm121/gb10_forum53_mtp2_epoff_c2_gmem0685_mml81920/20260612074113Profile:
max_model_len=81920,max_num_seqs=2,max_num_batched_tokens=4096,gpu_memory_utilization=0.685.forum53_c2:2:2:3200:256.Gate result:
oktrueserve_start.exit_code0streaming_pressure.exit_code0ok=true, signal count00 / 40Timing and runtime summary:
Running the NVFP4 checkpoint
This branch also serves
nvidia/DeepSeek-V4-Flash-NVFP4on SM12x (RTX PRO 6000 / GB10). The NVFP4 MoE auto-selects the FlashInfer CUTLASS backend (the SwiGLU-clamp model gate now accepts it), so no--moe-backendflag is required, and no special FlashInfer build is needed (the 0.6.12 release works):--kv-cache-dtype fp8is mandatory: DeepSeek-V4'sfp8_ds_mlaattention asserts an fp8 KV layout, so the defaultautofails at model construction (this is not NVFP4-specific). Expert-parallel off (plain TP) is the supported path.Accuracy matches MXFP4 (GSM8K 8-shot ~0.96 on both SM120 and SM121). Note that on SM12x NVFP4 is not a memory or throughput win versus MXFP4: NVFP4 weights are ~4 GiB/GPU larger (~78 vs ~74 GiB), leaving less KV-cache room (lower max concurrency); single-stream prefill is marginally faster and aggregate decode marginally slower. Its value here is checkpoint availability / parity with the SM100 datacenter path, not an SM12x performance advantage — MXFP4 remains the better practical choice on consumer Blackwell.
AI assistance disclosure
AI assistants, including OpenAI Codex/GPT models and Anthropic Claude models, were used for code review, refactoring support, regression-script writing, and benchmark analysis. The branch was validated through human review plus the commands and harness artifacts listed above.